跳到主要内容

hooks 进阶

React 中,useReduceruseMemouseCallback 是用于优化性能和管理状态的重要的 Hook。

它们的底层原理都与 React渲染机制依赖追踪记忆化(memoization)策略密切相关

注意

优先考虑代码的可读性,在性能瓶颈处针对性使用优化 Hook。过度使用 useMemo/useCallback 可能适得其反

一、底层原理

1. useReducer

useReducer 是一个状态管理的 Hook,它接收一个 reducer 函数和一个初始状态 initialState。返回一个当前状态和一个 dispatch 函数。

React 内部维护一个状态管理单元(类似于 Redux),储存在 Fiber 节点中 memoizedState 属性(以链表形式储存)的 (state, dispatch) 对,包含:

  • state:当前状态值

  • dispatch:触发更新的函数

  • reducer:纯函数 (state, action) => newState

  • 初始化:在组件首次渲染时,useReducer 会调用 reducer(initialState, undefined) 来计算初始状态并存储

  • 存储状态:React 内部为每个 Hook 创建一个 Hook 对象,该对象包含 memoizedState(存储当前状态)和 queue(存储待处理的 action 队列)

  • dispatch 函数:返回的 dispatch 函数是一个闭包,它持有对当前 Hook 对象和 reducer 函数的引用。当 dispatch(action) 被调用时:

    • action 添加到 Hook 对象的 queue
    • 触发组件的重新渲染(调度更新)
  • 重新渲染时:在组件函数再次执行时,useReducer 会:

    • Hook 对象的 queue 中取出所有待处理的 action
    • 按顺序应用 reducer 函数:newState = reducer(oldState, action)
    • 将最终计算出的状态作为当前状态返回,并清空 queue

dispatch 在组件生命周期内保持不变(依赖不变时),避免子组件无效重新渲染

伪代码实现
function useReducer(reducer, initialState) {
// 从当前的 Fiber 的 memoizedState 中获取当前的 Hook(依赖调用顺序)
let hook = mountWorkInProgressHook();
// 储存当前的状态
hook.memoizedState = hook.baseState = initialState;
// 储存待处理的 action 列队
const queue = (hook.queue = {
pending: null,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialState,
});

/** 是一个闭包,持有当前 `hook` 对象和 `reducer` 函数的引用 */
const dispatch = (queue.dispatch = dispatchSetState.bind(
null,
currentlyRenderingFiber,
queue,
));
return [hook.memoizedState, dispatch];
}

2. useMemo

在 Fiber 节点中储存 {value, dependencies},并使用 Object.is 比较依赖数组。

  • 储存Hook 对象储存 memoizedState(记忆化的值)和memoizedDeps(上一次的依赖项数组)
  • 首次渲染:执行传入的 factory 函数,将结果储存在 memoizedState 中,并将依赖项 deps 储存在 memoizedDeps
  • 更新渲染:比较当前传入的 deps 数组与 memoizedDeps 数组中的每一项(使用 Object.is 进行浅比较)
    • 如果所有依赖项都没有变化,则直接返回 memoizedState (缓存的值)
    • 如果任何一个依赖项发生变化,则重新执行 factory 函数,用新结果更新 memoizedState,并用新的 deps 更新 memoizedDeps
伪代码实现
function useMemo(factory, deps) {
const hook = mountWOrkInProgressHook();
// 对比依赖项是否变化
if (depsChanged(hook.memoizedState?.deps, deps)) {
// 子发生变化时更新储存的状态
hook.memoizedState = {
value: factory(),
deps,
};
}
return hook.memoizedState.value;
}

3. useCallback

本质是 useMemo 的语法糖。

useCallback(fn, deps) === useMemo(() => fn, deps);
  • 函数缓存:避免每次渲染创建新函数实例
  • 依赖比对:依赖变化时返回新函数

二、 useReduceruseState

特性useReduceruseState
状态更新通过 dispatch(action)直接 setState(newValue)
复杂状态✅ 更适合多状态关联⚠️ 易导致多次更新
逻辑分离reducer 抽离业务逻辑更新逻辑内联在组件
中间状态✅ 避免读写分离问题❌ 可能产生中间态
性能优化✅ 稳定 dispatch 引用需手动优化 setState
新能引入了 reducer 函数的调用,但对于复杂的状态,逻辑更清晰,可能减少不必要的重渲染对于简单的状态,性能较小
合适选择复杂状态/多操作简单状态

三、useCallbackuseMemo

特性useCallbackuseMemo
缓存目标函数任意计算结果(可以是任意类型:数字、字符串、对象、数组、函数等)
等价形式useMemo(() => fn, deps)--
主要用途稳定函数引用避免重复计算
典型场景传递给 memoized 子组件记忆化昂贵计算
// useCallback 用法
const handleClick = useCallback(() => {
console.log('clicked');
}, []);

// useMemo 用法
const sortedList = useMemo(() => {
return list.sort((a, b) => a - b);
}, [list]);

四、 useMemoReact.memo

特性useMemoReact.memo
作用层级组件内部的 Hook组件外包裹的高阶组件(HOC)或组件包装器
优化目标避免内部重复计算避免整个组件重渲染
依赖关系控制具体值的缓存浅比较 props
配合使用常与 memo 搭配稳定 props需稳定 props 输入
性能优化点优化渲染过程中的计算开销优化组件的渲染过程的本身(是否需要重新 render
使用场景计算派生数据(如,过滤,排序);创建复杂的对或数组;记忆化回调函数(useCallback子组件接收的 props 经常不变;子组件渲染开销大;防止父组件重渲染导致不必要的子组件重渲染
const Child = React.memo(({ onClick }) => {
// 避免子组件渲染
});

function Parent() {
const handleClick = useCallback(() => {}, []);
// useMemo 可优化传递的对象类型 prop
return <Child onClick={handleClick} />;
}

五、 一个简单的小例

const data = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const update = useCallback(() => handler(data), [data]);

return <MemoizedComponent data={data} onClick={update} />;

六、useContextuseReducer 结合使用

二者结合的价值是:useReducer 管理复杂状态,用 useContext 共享状态和更新函数(dispatch

  • useReducer 集中管理状态和更新逻辑(代替使用分散的 useState
  • useContext 将状态和 dispatch 传递给多有需要的子组件,避免逐层传递 props
主题切换
// 1. 定义 reducer 管理主题状态
function themeReducer(state, action) {
switch (action.type) {
case 'TOGGLE_THEME':
return { ...state, isDark: !state.isDark };
default:
return state;
}
}

// 2. 创建 Context
const ThemeContext = createContext();

// 3. 父组件提供状态和 `dispatch`
function App() {
const [themeState, dispatch] = useReducer(themeReducer, { isDark: false });

return (
<ThemeContext.Provider value={{ themeState, dispatch }}>
<Header />
<Content />
</ThemeContext.Provider>
);
}

// 4. 深层子组件直接使用 `dispatch` 更新状态
function Header() {
const { themeState, dispatch } = useContext(ThemeContext);

return (
<button onClick={() => dispatch({ type: 'TOGGLE_THEME' })}>
{themeState.isDark ? '切换亮色模式' : '切换暗色模式'}
</button>
);
}
  • 当多个状态存在依赖关系时(如表单验证),用 useReducer 合并状态为一个对象,通过 dispatch 统一更新,避免因多个 useState 导致的多次渲染。结合 useContext 后,子组件只需订阅一次上下文,减少不必要的重渲染
  • reducer 函数可以被多个组件或模块复用(如不同的页面共享同一状态更新逻辑),而 useContext 确保状态和更新函数的一致性